#include <iostream>
#include <httpserver.hpp>

#include "utils.h"
#include "darknet_yolo.h"
#include "caffe_insight.h"
#include "httpclient.h"

#include <sys/types.h>
#include <sys/fcntl.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>

using namespace httpserver;


//mutex
pthread_mutex_t culock; //cuda
pthread_mutex_t dblock; //db
pthread_mutex_t qelock; //queue
pthread_mutex_t stlock; //socket

IniParser* g_iniparser;
DarknetYolo* g_fdetector = NULL;
CaffeInsight* g_finsight = NULL;
std::vector<cv::Vec2f> g_stdAlignPoints;
std::vector<cv::Vec2f> g_stdPostPoints;

cv::Mat FEATURE_MEANVAL;

typedef struct DBFace {
  int picid = -1;
  int personid = -1;
  float feature[1024] = {0};
} DBFace;
std::vector<DBFace> g_DBFaceVec;

typedef struct Portrait {
  int camid = -1;
  cv::Mat image;
  cv::Mat portrait;
} Portrait;
std::queue<std::shared_ptr<Portrait>> g_prtraQueue;

typedef struct Dets {
  int camid = -1;
  cv::Mat image;
  cv::Mat portrait;
  int nboxes = 0;
  detection* dets = NULL;
  int topidx = -1;
  float topobj = .0f;
  ~Dets(){free_detections(this->dets,this->nboxes);};
} Dets;
std::queue<std::shared_ptr<Dets>> g_detsQueue;

typedef struct Face {
  int camid = -1;
  cv::Mat image;
  cv::Mat portrait;
  cv::Mat aligns[2];
  float feature[1024] = {0};
  float objness = .0f;
} Face;
std::queue<std::shared_ptr<Face>> g_faceQueue;

typedef struct Post {
  int camid = -1;
  cv::Mat image;
  cv::Mat portrait;
  cv::Mat aligns[2];
  float feature[1024] = {0};
  float objness = .0f;
  int personid = -1;
  int picid = -1;
  float score = .0f;
  int missnum = 0;
  int hitnum = 0;
} Post;
std::queue<std::shared_ptr<Post>> g_postQueue;

const int max_queue = 64;
std::chrono::high_resolution_clock::time_point g_idlestart = std::chrono::high_resolution_clock::now();
HttpClient* client = new HttpClient();

std::vector<int> g_sendconns(0);
std::vector<int> g_recvconns(0);
#define IMG_WIDTH 640
#define IMG_HEIGHT 480
#define BUFFER_SIZE IMG_WIDTH*IMG_HEIGHT*3/32 //default CV_8UC3
struct recvbuf
{
  char buf[BUFFER_SIZE];
  int flag;
};
struct sendbuf
{
  int camid;
  int picid;
  int personid;
};


class feature_calculation : public http_resource {
public:
  const std::shared_ptr<http_response> render(const http_request& req) {
    return std::shared_ptr<http_response>(new string_response("This is /face/featurecal."));
  }
  const std::shared_ptr<http_response> render_POST(const http_request& req) {
    std::string content = req.get_content();

    auto pt = string2json(content);
    int picid = pt.get<int>("id");
    std::string sportrait = pt.get<std::string>("portrait");

    cv::Mat portrait = Base2Mat(sportrait);
    //cv::imwrite("portrait.jpg", portrait);

    //json
    boost::property_tree::ptree json, rejson;
    rejson.put<int>("code", 200);
    rejson.put("msg", "success");
    json.put<int>("id", picid);

    //obj thresh
    float objthre = g_iniparser->GetFloat("objthre", "setting");


    int nboxes = 0;
    detection* dets = NULL;

    //detec
    if (!portrait.empty()) {
      pthread_mutex_lock(&culock); //lock cuda
      dets = g_fdetector->Getdets(portrait, &nboxes);
      pthread_mutex_unlock(&culock); //unlock cuda
    }

    int topidx = -1;
    float topobj = .0f;

    //search top1
    for (int i = 0; i < nboxes; i++) {
      if (0.01 > dets[i].prob[0]) continue;
      if (objthre > dets[i].objectness) continue;
      if (dets[i].prob[0]*dets[i].objectness > topobj) {
        topidx = i;
        topobj = dets[i].prob[0]*dets[i].objectness;
      }
    }

    float feature[1024];
    //GPU Model
    pthread_mutex_lock(&culock); //lock cuda
    g_finsight->SetGPUMode();
    pthread_mutex_unlock(&culock); //unlock cuda

    //insightface
    if (topidx >= 0) {
      box b = dets[topidx].bbox;

      std::vector<cv::Vec2f> currentROIPoints;
      currentROIPoints.push_back(cv::Vec2f((b.lx1)*portrait.cols, (b.ly1)*portrait.rows));
      currentROIPoints.push_back(cv::Vec2f((b.lx2)*portrait.cols, (b.ly2)*portrait.rows));
      currentROIPoints.push_back(cv::Vec2f((b.lx3)*portrait.cols, (b.ly3)*portrait.rows));
      currentROIPoints.push_back(cv::Vec2f((b.lx4)*portrait.cols, (b.ly4)*portrait.rows));
      currentROIPoints.push_back(cv::Vec2f((b.lx5)*portrait.cols, (b.ly5)*portrait.rows));

      cv::Mat resultImg(112, 96, CV_8UC3), f_mat;
      cv::Mat warp_mat = estimateRigidTransform_l(currentROIPoints, g_stdAlignPoints, false);
      cv::warpAffine(portrait, resultImg, warp_mat, resultImg.size());
      //cv::imwrite("person.jpg", resultImg);

      cv::flip(resultImg, f_mat, 1);
      //cv::imwrite("personflip.jpg", f_mat);

      cv::Mat aligns[2];
      aligns[0] = resultImg;
      aligns[1] = f_mat;

      pthread_mutex_lock(&culock); //lock cuda
      g_finsight->Predict(aligns, feature, 2, "fc5");
      pthread_mutex_unlock(&culock); //unlock cuda

      std::string sfeature = Float1024ToString(feature);
      json.put("feature", sfeature);
    }
    else {
      rejson.put<int>("code", 400);
      rejson.put("msg", "failure");
      json.put("feature", "");
    }

    //resp
    rejson.add_child("data", json);
    std::string resp = json2string(rejson);
    return std::shared_ptr<http_response>(new string_response(resp));
  }
};

class face_recognition : public http_resource {
public:
  const std::shared_ptr<http_response> render(const http_request& req) {
    return std::shared_ptr<http_response>(new string_response("This is /face/recognition."));
  }
  const std::shared_ptr<http_response> render_POST(const http_request& req) {
    std::string content = req.get_content();

    auto pt = string2json(content);
    int picid = pt.get<int>("id");
    std::string sportrait = pt.get<std::string>("portrait");

    cv::Mat portrait = Base2Mat(sportrait);
    //cv::imwrite("portrait.jpg", portrait);

    //json
    boost::property_tree::ptree json, rejson;
    rejson.put<int>("code", 200);
    rejson.put("msg", "success");
    json.put<int>("id", picid);

    //obj thresh
    float objthre = g_iniparser->GetFloat("objthre", "setting");


    int nboxes = 0;
    detection* dets = NULL;

    //detec
    if (!portrait.empty()) {
      pthread_mutex_lock(&culock); //lock cuda
      dets = g_fdetector->Getdets(portrait, &nboxes);
      pthread_mutex_unlock(&culock); //unlock cuda
    }

    int topidx = -1;
    float topobj = .0f;

    //search top1
    for (int i = 0; i < nboxes; i++) {
      if (0.01 > dets[i].prob[0]) continue;
      if (objthre > dets[i].objectness) continue;
      if (dets[i].prob[0]*dets[i].objectness > topobj) {
        topidx = i;
        topobj = dets[i].prob[0]*dets[i].objectness;
      }
    }

    float feature[1024];
    //GPU Model
    pthread_mutex_lock(&culock); //lock cuda
    g_finsight->SetGPUMode();
    pthread_mutex_unlock(&culock); //unlock cuda

    //insightface
    if (topidx >= 0) {
      box b = dets[topidx].bbox;

      std::vector<cv::Vec2f> currentROIPoints;
      currentROIPoints.push_back(cv::Vec2f((b.lx1)*portrait.cols, (b.ly1)*portrait.rows));
      currentROIPoints.push_back(cv::Vec2f((b.lx2)*portrait.cols, (b.ly2)*portrait.rows));
      currentROIPoints.push_back(cv::Vec2f((b.lx3)*portrait.cols, (b.ly3)*portrait.rows));
      currentROIPoints.push_back(cv::Vec2f((b.lx4)*portrait.cols, (b.ly4)*portrait.rows));
      currentROIPoints.push_back(cv::Vec2f((b.lx5)*portrait.cols, (b.ly5)*portrait.rows));

      cv::Mat resultImg(112, 96, CV_8UC3), f_mat;
      cv::Mat warp_mat = estimateRigidTransform_l(currentROIPoints, g_stdAlignPoints, false);
      cv::warpAffine(portrait, resultImg, warp_mat, resultImg.size());
      //cv::imwrite("person.jpg", resultImg);

      cv::flip(resultImg, f_mat, 1);
      //cv::imwrite("personflip.jpg", f_mat);

      cv::Mat aligns[2];
      aligns[0] = resultImg;
      aligns[1] = f_mat;

      pthread_mutex_lock(&culock); //lock cuda
      g_finsight->Predict(aligns, feature, 2, "fc5");
      pthread_mutex_unlock(&culock); //unlock cuda

      //#recognition
      float topscore = .0f;
      unsigned int topidx = 0;

      pthread_mutex_lock(&dblock); //lock db
      int dbsize = g_DBFaceVec.size();
      for (unsigned int i = 0; i < g_DBFaceVec.size(); i++) {
        float* fptr = g_DBFaceVec[i].feature;
        //score
        float score = feature_cmp1024(feature, fptr, FEATURE_MEANVAL);
        if (score > topscore) {
          topidx = i;
          topscore = score;
        }
      }
      pthread_mutex_unlock(&dblock); //unlock db

      if (dbsize > 0) {
        json.put<int>("personid", g_DBFaceVec[topidx].personid);
        json.put<int>("picid", g_DBFaceVec[topidx].picid);
        json.put<float>("score", topscore);
      }
      else {
        //nobody in db
        rejson.put<int>("code", 404);
        rejson.put("msg", "nobody in db");
      }

    }
    else {
      rejson.put<int>("code", 400);
      rejson.put("msg", "failure");
    }

    //resp
    rejson.add_child("data", json);
    std::string resp = json2string(rejson);
    return std::shared_ptr<http_response>(new string_response(resp));
  }
};

class face_analysis : public http_resource {
public:
  const std::shared_ptr<http_response> render(const http_request& req) {
    return std::shared_ptr<http_response>(new string_response("This is /face/analysis."));
  }
  const std::shared_ptr<http_response> render_POST(const http_request& req) {
    std::string content = req.get_content();

    auto pt = string2json(content);
    int camid = pt.get<int>("camid");
    std::string sportrait = pt.get<std::string>("portrait");

    cv::Mat portrait = Base2Mat(sportrait);
    //cv::imwrite("portrait.jpg", portrait);

    //json
    boost::property_tree::ptree json;
    json.put<int>("code", 200);
    json.put("msg", "success");
    json.put<int>("camid", camid);

    std::shared_ptr<Portrait> data(new Portrait());
    data->camid = camid;
    data->portrait = portrait;

    pthread_mutex_lock(&qelock);   //lock
    if (g_prtraQueue.size() >= max_queue) {
      g_prtraQueue.pop();
      g_prtraQueue.push(data);
    }
    else {
      g_prtraQueue.push(data);
    }
    int qsize = g_prtraQueue.size();
    pthread_mutex_unlock(&qelock); //unlock

    std::cout << "[INFO]: POST >> [face_analysis] @@@@ camid: " << camid << ", portrait queue size: " << qsize << std::endl;

    //resp
    std::string resp = json2string(json);
    return std::shared_ptr<http_response>(new string_response(resp));
  }
};


void *thread_tcp_listen(void* arg)
{
  int port = g_iniparser->GetInt("port", "socket");

  struct sockaddr_in channel; // holds IP address
  //Build address structure to bind to socket
  memset(&channel, 0, sizeof(channel));
  channel.sin_family = AF_INET;
  channel.sin_addr.s_addr = htonl(INADDR_ANY);
  channel.sin_port = htons(port);

  //Passive open - Wait for connection
  int skt = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
  if(skt < 0) {
    std::cerr << "[Error]: tcp socket failed." << std::endl;
    return 0;
  }

  int on=1;
  setsockopt(skt, SOL_SOCKET, SO_REUSEADDR, (char*)&on, sizeof(on));
  int nRecvBuf = 1024 * 1024 * 10;  //10M
  setsockopt(skt, SOL_SOCKET, SO_RCVBUF, (const char*)&nRecvBuf, sizeof(int));
  struct timeval timeout = {0,500000}; //500ms
  setsockopt(skt, SOL_SOCKET, SO_SNDTIMEO, (char *)&timeout,sizeof(struct timeval));
  setsockopt(skt, SOL_SOCKET, SO_RCVTIMEO, (char *)&timeout,sizeof(struct timeval));

  // NoBlock setting
  // int flags = fcntl(skt, F_GETFL, 0);
  // fcntl(skt, F_SETFL, flags|O_NONBLOCK);

  int bid = bind(skt, (struct sockaddr *)&channel, sizeof(channel));
  if (bid < 0) {
    std::cerr << "[Error]: tcp socket bind failed.." << std::endl;
    return 0;
  }
  int lsn = listen(skt, max_queue);
  if (lsn < 0) {
    std::cerr << "[Error]: listen failed." << std::endl;
    return 0;
  }

  std::cout << "TCP Server is Running on: " << port << std::endl;

  while (1) {
    //block on accept, success or -1.
    struct sockaddr_in client_addr;
    socklen_t length = sizeof(client_addr);
    int conn = accept(skt, (struct sockaddr*)&client_addr,&length);

    if(conn<0) {
      //std::cout << "[Error]: Find a invalid tcp connecting." << std::endl;
      continue;
    }
    std::cout << "[INFO]: new tcp image client accepted." << std::endl;

    pthread_mutex_lock(&stlock);   //lock
    g_sendconns.push_back(conn);
    pthread_mutex_unlock(&stlock); //unlock
  }
}

void *thread_tcp_post_listen(void* arg)
{
  int port = g_iniparser->GetInt("post", "socket");

  struct sockaddr_in channel; // holds IP address
  //Build address structure to bind to socket
  memset(&channel, 0, sizeof(channel));
  channel.sin_family = AF_INET;
  channel.sin_addr.s_addr = htonl(INADDR_ANY);
  channel.sin_port = htons(port);

  //Passive open - Wait for connection
  int skt = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
  if(skt < 0) {
    std::cerr << "[Error]: tcp socket failed." << std::endl;
    return 0;
  }

  int on=1;
  setsockopt(skt, SOL_SOCKET, SO_REUSEADDR, (char*)&on, sizeof(on));
  int nRecvBuf = 1024 * 1024 * 10;  //10M
  setsockopt(skt, SOL_SOCKET, SO_RCVBUF, (const char*)&nRecvBuf, sizeof(int));
  struct timeval timeout = {0,500000}; //500ms
  setsockopt(skt, SOL_SOCKET, SO_SNDTIMEO, (char *)&timeout,sizeof(struct timeval));
  setsockopt(skt, SOL_SOCKET, SO_RCVTIMEO, (char *)&timeout,sizeof(struct timeval));

  //NoBlock setting
  int flags = fcntl(skt, F_GETFL, 0);
  fcntl(skt, F_SETFL, flags|O_NONBLOCK);

  int bid = bind(skt, (struct sockaddr *)&channel, sizeof(channel));
  if (bid < 0) {
    std::cerr << "[Error]: tcp socket bind failed.." << std::endl;
    return 0;
  }
  int lsn = listen(skt, max_queue);
  if (lsn < 0) {
    std::cerr << "[Error]: listen failed." << std::endl;
    return 0;
  }

  std::cout << "TCP POST Server is Running on: " << port << std::endl;

  while (1) {
    //block on accept, success or -1.
    struct sockaddr_in client_addr;
    socklen_t length = sizeof(client_addr);
    int conn = accept(skt, (struct sockaddr*)&client_addr,&length);

    if(conn<0) {
      //std::cout << "[Error]: Find a invalid tcp connecting." << std::endl;
      continue;
    }
    std::cout << "[INFO]: new tcp post client accepted." << std::endl;

    pthread_mutex_lock(&stlock);   //lock
    g_recvconns.push_back(conn);
    pthread_mutex_unlock(&stlock); //unlock
  }
}

void *thread_tcp_image(void* arg)
{
  while (true) {

    pthread_mutex_lock(&stlock);   //lock
    int skts = g_sendconns.size();
    pthread_mutex_unlock(&stlock); //unlock

    if (skts < 1) continue;

    for (int ski = 0; ski < skts; ski++) {

      int count = 0;
      int needRecv = sizeof(recvbuf);
      cv::Mat image, img(IMG_HEIGHT, IMG_WIDTH, CV_8UC3, cv::Scalar(0));

      struct recvbuf data;
      int connstoped = 0;

      auto start = std::chrono::high_resolution_clock::now();

      for (int i = 0; i < 32; i++) {

        int pos = 0;
        int len0 = 0;

        while (pos < needRecv) {
          len0 = recv(g_sendconns[ski], (char*)(&data) + pos, needRecv - pos, 0);
          if (len0 < 0) {
              std::cerr << "[Error]: tcp image client disconnect!" << std::endl;
              connstoped = 1;
              break;
          }
          pos += len0;
        }
        count = count + data.flag;

        if (connstoped) {
          close(g_sendconns[ski]);       //close
          pthread_mutex_lock(&stlock);   //lock
          g_sendconns.erase(g_sendconns.begin()+ski);
          skts = g_sendconns.size();
          pthread_mutex_unlock(&stlock); //unlock
          ski--;
          break;
        }

        int num1 = IMG_HEIGHT / 32 * i;
        for (int j = 0; j < IMG_HEIGHT / 32; j++)
        {
          int num2 = j * IMG_WIDTH * 3;
          uchar* ucdata = img.ptr<uchar>(j + num1);
          for (int k = 0; k < IMG_WIDTH * 3; k++) {
            ucdata[k] = data.buf[num2 + k];
          }
        }

        if (data.flag == 2)
        {
          if (count == 33)
          {
            image = img;
            //return 1;
            count = 0;
          } else {
            count = 0;
            i = 0;
          }
        }
      }
      if (image.empty()) continue;
      //cv::imwrite("image.jpg", image);

      std::shared_ptr<Portrait> pimage(new Portrait());
      pimage->camid = 0;
      pimage->portrait = image;

      pthread_mutex_lock(&qelock);   //lock
      if (g_prtraQueue.size() >= max_queue) {
        g_prtraQueue.pop();
        g_prtraQueue.push(pimage);
      }
      else {
        g_prtraQueue.push(pimage);
      }
      pthread_mutex_unlock(&qelock); //unlock
      std::cout << "[INFO]: TCP >> read image. [camid]: " << pimage->camid << " [time]: " << std::chrono::duration<double, std::milli>(std::chrono::high_resolution_clock::now()-start).count() << "ms" << std::endl;
    }
  }
}

void *thread_tcp_post(void* arg)
{
  std::vector<std::shared_ptr<Post>> postbuffer;

  float simthre = g_iniparser->GetFloat("simthre", "face");
  float cmpthre = g_iniparser->GetFloat("cmpthre", "face");
  int vacuump = g_iniparser->GetInt("vacuump", "face");
  int missnum = g_iniparser->GetInt("missnum", "face");
  int hitnum = g_iniparser->GetInt("hitnum", "face");

  while (true) {

    pthread_mutex_lock(&qelock);   //lock
    int qsize = g_postQueue.size();
    pthread_mutex_unlock(&qelock); //unlock

    if (qsize > 0) {

      pthread_mutex_lock(&qelock);   //lock
      std::shared_ptr<Post> data = g_postQueue.front();
      g_postQueue.pop();
      int pqsize = g_postQueue.size();
      pthread_mutex_unlock(&qelock); //unlock

      auto start = std::chrono::high_resolution_clock::now();


      bool foundInKBuffer = false;
      bool foundDriveout = false;

      for (int i = 0; i < postbuffer.size(); i++) {
        bool driveout = false;
        if (data->camid != postbuffer[i]->camid) continue;
        float sscore = feature_cmp1024(data->feature, postbuffer[i]->feature, FEATURE_MEANVAL);

        if (sscore > simthre) {
          foundInKBuffer = true;
          postbuffer[i]->hitnum++;
          data->hitnum = postbuffer[i]->hitnum;
        } else {
          postbuffer[i]->missnum++;
        }
        if ((sscore > simthre) && (data->score > postbuffer[i]->score)) { driveout = true; }

        if (driveout) {
          postbuffer.erase(postbuffer.begin()+i);
          foundDriveout = true;
          i--;
        }
      }
      if (!foundInKBuffer || foundDriveout) { postbuffer.push_back(data); }


      std::cout << "[INFO]: [postbuffer] @@@@@@ [round time]: " << std::chrono::duration<double, std::milli>(std::chrono::high_resolution_clock::now()-start).count() << "ms" << " [postbuffer size]: " << postbuffer.size() << std::endl;

    }

    //post round
    for (int i = 0; i < postbuffer.size(); i++) {
      bool needpost = false;
      if (postbuffer[i]->missnum >= missnum) { needpost = true; }
      if (std::chrono::duration<double>(std::chrono::high_resolution_clock::now()-g_idlestart).count() > vacuump) {
        needpost = true;
      }

      auto start = std::chrono::high_resolution_clock::now();

      if (needpost) {
        int camid = postbuffer[i]->camid;
        int picid = postbuffer[i]->picid;
        int personid = postbuffer[i]->personid;
        //auto now = std::chrono::system_clock::now();
        //std::string timestamp = chrono_to_string(now);
        //json.put<std::string>("timestamp", timestamp);

        struct sendbuf msg;
        msg.camid = camid;
        msg.picid = picid;
        msg.personid = personid;

        pthread_mutex_lock(&stlock);   //lock
        int skts = g_recvconns.size();
        pthread_mutex_unlock(&stlock); //unlock

        for (int ski = 0; ski < skts; ski++) {
          if (send(g_recvconns[ski], (char*)(&msg), sizeof(msg), 0) < 0) {
              std::cerr << "[Error]: tcp image client disconnect!" << std::endl;
              close(g_recvconns[ski]);       //close
              pthread_mutex_lock(&stlock);   //lock
              g_recvconns.erase(g_recvconns.begin()+ski);
              skts = g_recvconns.size();
              pthread_mutex_unlock(&stlock); //unlock
              ski--;
          }
        }

        std::cout << "[INFO]: post. @@@@@@@@ [round time]: " << std::chrono::duration<double, std::milli>(std::chrono::high_resolution_clock::now()-start).count() << "ms" << std::endl;

        //erase
        postbuffer.erase(postbuffer.begin()+i);
        i--;
      }
    }

  }
}

void *thread_detection(void* arg)
{
  //obj thresh
  float objthre = g_iniparser->GetFloat("objthre", "setting");

  while (true) {

    pthread_mutex_lock(&qelock);   //lock
    int qsize = g_prtraQueue.size();
    pthread_mutex_unlock(&qelock); //unlock

    if (qsize < 1) continue;

    pthread_mutex_lock(&qelock);   //lock
    std::shared_ptr<Portrait> data = g_prtraQueue.front();
    g_prtraQueue.pop();
    pthread_mutex_unlock(&qelock); //unlock

    if (data->portrait.empty()) continue;

    auto start = std::chrono::high_resolution_clock::now();

    int nboxes = 0;
    detection* dets = NULL;

    pthread_mutex_lock(&culock); //lock cuda
    dets = g_fdetector->Getdets(data->portrait, &nboxes);
    pthread_mutex_unlock(&culock); //unlock cuda

    ////draw
    //cv::Mat image;
    //data->portrait.copyTo(image);
    //cv::imwrite("image1.jpg", image);
    //for (int i = 0; i < nboxes; i++)
    //{
    //  if (0.01 > dets[i].prob[0]) continue;

    //  box b = dets[i].bbox;
    //  int left = (b.x - b.w / 2.)*image.cols;
    //  int right = (b.x + b.w / 2.)*image.cols;
    //  int top = (b.y - b.h / 2.)*image.rows;
    //  int bot = (b.y + b.h / 2.)*image.rows;

    //  int lx1 = (b.lx1)*image.cols;
    //  int ly1 = (b.ly1)*image.rows;
    //  int lx2 = (b.lx2)*image.cols;
    //  int ly2 = (b.ly2)*image.rows;
    //  int lx3 = (b.lx3)*image.cols;
    //  int ly3 = (b.ly3)*image.rows;
    //  int lx4 = (b.lx4)*image.cols;
    //  int ly4 = (b.ly4)*image.rows;
    //  int lx5 = (b.lx5)*image.cols;
    //  int ly5 = (b.ly5)*image.rows;

    //  if (left < 0) left = 0;
    //  if (right > image.cols - 1) right = image.cols - 1;
    //  if (top < 0) top = 0;
    //  if (bot > image.rows - 1) bot = image.rows - 1;

    //  cv::rectangle(image, cv::Point(left,top), cv::Point(right,bot), cv::Scalar(255, 0, 0), 2);
    //}
    //cv::imwrite("prediction.jpg", image);

    //search top1
    int topidx = -1;
    float topobj = .0f;

    for (int i = 0; i < nboxes; i++) {
      if (0.01 > dets[i].prob[0]) continue;
      if (objthre > dets[i].objectness) continue;
      if (dets[i].prob[0]*dets[i].objectness > topobj) {
        topidx = i;
        topobj = dets[i].prob[0]*dets[i].objectness;
      }
    }

    std::shared_ptr<Dets> result(new Dets());
    result->camid = data->camid;
    result->portrait = data->portrait;
    result->nboxes = nboxes;
    result->dets = dets;
    result->topidx = topidx;
    result->topobj = topobj;

    pthread_mutex_lock(&qelock);   //lock
    if (g_detsQueue.size() >= max_queue) {
      g_detsQueue.pop();
      g_detsQueue.push(result);
    }
    else {
      g_detsQueue.push(result);
    }
    int dqsize = g_detsQueue.size();
    pthread_mutex_unlock(&qelock); //unlock

    std::cout << "[INFO]: THD  >> [dets] @@@@ [camid]: " << data->camid << ", [time]: " << std::chrono::duration<double, std::milli>(std::chrono::high_resolution_clock::now()-start).count() << "ms" << " [dets queue size]: " << dqsize << std::endl;
  }
}

void *thread_insightface(void* arg)
{
  while (true) {

    pthread_mutex_lock(&qelock);   //lock
    int qsize = g_detsQueue.size();
    pthread_mutex_unlock(&qelock); //unlock

    if (qsize < 1) continue;

    pthread_mutex_lock(&qelock);   //lock
    std::shared_ptr<Dets> data = g_detsQueue.front();
    g_detsQueue.pop();
    pthread_mutex_unlock(&qelock); //unlock

    if (data->topidx < 0) continue;

    auto start = std::chrono::high_resolution_clock::now();


    box b = data->dets[data->topidx].bbox;

    std::vector<cv::Vec2f> currentROIPoints;
    currentROIPoints.push_back(cv::Vec2f((b.lx1)*data->portrait.cols, (b.ly1)*data->portrait.rows));
    currentROIPoints.push_back(cv::Vec2f((b.lx2)*data->portrait.cols, (b.ly2)*data->portrait.rows));
    currentROIPoints.push_back(cv::Vec2f((b.lx3)*data->portrait.cols, (b.ly3)*data->portrait.rows));
    currentROIPoints.push_back(cv::Vec2f((b.lx4)*data->portrait.cols, (b.ly4)*data->portrait.rows));
    currentROIPoints.push_back(cv::Vec2f((b.lx5)*data->portrait.cols, (b.ly5)*data->portrait.rows));

    cv::Mat resultImg(112, 96, CV_8UC3), f_mat;
    cv::Mat warp_mat = estimateRigidTransform_l(currentROIPoints, g_stdAlignPoints, false);
    cv::warpAffine(data->portrait, resultImg, warp_mat, resultImg.size());
    //cv::imwrite("person.jpg", resultImg);

    cv::flip(resultImg, f_mat, 1);
    //cv::imwrite("personflip.jpg", f_mat);

    cv::Mat aligns[2];
    aligns[0] = resultImg;
    aligns[1] = f_mat;

    //GPU Model
    pthread_mutex_lock(&culock); //lock cuda
    g_finsight->SetGPUMode();
    pthread_mutex_unlock(&culock); //unlock cuda


    std::shared_ptr<Face> result(new Face());
    result->camid = data->camid;
    result->portrait = resultImg;  //iiiiiimage
    result->aligns[0] = aligns[0];
    result->aligns[1] = aligns[1];
    result->objness = data->dets[data->topidx].objectness;

    pthread_mutex_lock(&culock); //lock cuda
    g_finsight->Predict(aligns, result->feature, 2, "fc5");
    pthread_mutex_unlock(&culock); //unlock cuda

    pthread_mutex_lock(&qelock);   //lock
    g_faceQueue.push(result);
    int fqsize = g_faceQueue.size();
    pthread_mutex_unlock(&qelock); //unlock

    std::cout << "[INFO]: THD  >> [insightface] @@@@ [camid]: " << data->camid << ", [time]: " << std::chrono::duration<double, std::milli>(std::chrono::high_resolution_clock::now()-start).count() << "ms" << " [face queue size]: " << fqsize << std::endl;
  }
}

void *thread_compare(void* arg)
{
  while (true) {

    pthread_mutex_lock(&qelock);   //lock
    int qsize = g_faceQueue.size();
    pthread_mutex_unlock(&qelock); //unlock

    if (qsize < 1) continue;

    g_idlestart = std::chrono::high_resolution_clock::now();  //bug!!!!!!!!!!!!!!!!!!

    pthread_mutex_lock(&qelock);   //lock
    std::shared_ptr<Face> data = g_faceQueue.front();
    g_faceQueue.pop();
    pthread_mutex_unlock(&qelock); //unlock

    auto start = std::chrono::high_resolution_clock::now();


    //#recognition
    unsigned int topidx = 0;
    float topscore = .0f;
    int personid = -1;
    int picid = -1;

    pthread_mutex_lock(&dblock); //lock db
    int dbsize = g_DBFaceVec.size();
    for (unsigned int i = 0; i < g_DBFaceVec.size(); i++) {
      float* fptr = g_DBFaceVec[i].feature;
      //score
      float score = feature_cmp1024(data->feature, fptr, FEATURE_MEANVAL);
      if (score > topscore) {
        topidx = i;
        topscore = score;
        personid = g_DBFaceVec[i].personid;
        picid = g_DBFaceVec[i].picid;
      }
    }
    pthread_mutex_unlock(&dblock); //unlock db


    std::shared_ptr<Post> result(new Post());
    result->camid = data->camid;
    result->portrait = data->portrait;
    result->aligns[0] = data->aligns[0];
    result->aligns[1] = data->aligns[1];
    result->objness = data->objness;
    result->score = topscore;
    result->personid = personid;
    result->picid = picid;
    memcpy(result->feature, data->feature, sizeof(float)*1024);
    //std::cout << "[INFO]: [memcpy]: result->feature[1024]: " << result->feature[1023] << ", data->feature[1024]: " << data->feature[1023] << std::endl;

    pthread_mutex_lock(&qelock);   //lock
    g_postQueue.push(result);
    int pqsize = g_postQueue.size();
    pthread_mutex_unlock(&qelock); //unlock

    std::cout << "[INFO]: THD  >> [compare] @@@@ [camid]: " << data->camid << ", [time]: " << std::chrono::duration<double, std::milli>(std::chrono::high_resolution_clock::now()-start).count() << "ms" << " [post queue size]: " << pqsize << std::endl;
  }
}

void *thread_post(void* arg)
{
  std::vector<std::shared_ptr<Post>> postbuffer;

  float simthre = g_iniparser->GetFloat("simthre", "face");
  float cmpthre = g_iniparser->GetFloat("cmpthre", "face");
  int vacuump = g_iniparser->GetInt("vacuump", "face");
  int missnum = g_iniparser->GetInt("missnum", "face");
  int hitnum = g_iniparser->GetInt("hitnum", "face");

  while (true) {

    pthread_mutex_lock(&qelock);   //lock
    int qsize = g_postQueue.size();
    pthread_mutex_unlock(&qelock); //unlock

    if (qsize > 0) {

      pthread_mutex_lock(&qelock);   //lock
      std::shared_ptr<Post> data = g_postQueue.front();
      g_postQueue.pop();
      int pqsize = g_postQueue.size();
      pthread_mutex_unlock(&qelock); //unlock

      auto start = std::chrono::high_resolution_clock::now();


      bool foundInKBuffer = false;
      bool foundDriveout = false;

      for (int i = 0; i < postbuffer.size(); i++) {
        bool driveout = false;
        if (data->camid != postbuffer[i]->camid) continue;
        float sscore = feature_cmp1024(data->feature, postbuffer[i]->feature, FEATURE_MEANVAL);

        if (sscore > simthre) {
          foundInKBuffer = true;
          postbuffer[i]->hitnum++;
          data->hitnum = postbuffer[i]->hitnum;
        } else {
          postbuffer[i]->missnum++;
        }
        if ((sscore > simthre) && (data->score > postbuffer[i]->score)) { driveout = true; }

        if (driveout) {
          postbuffer.erase(postbuffer.begin()+i);
          foundDriveout = true;
          i--;
        }
      }
      if (!foundInKBuffer || foundDriveout) { postbuffer.push_back(data); }


      std::cout << "[INFO]: [postbuffer] @@@@@@ [round time]: " << std::chrono::duration<double, std::milli>(std::chrono::high_resolution_clock::now()-start).count() << "ms" << " [postbuffer size]: " << postbuffer.size() << std::endl;

    }


    //post round
    for (int i = 0; i < postbuffer.size(); i++) {
      bool needpost = false;
      if (postbuffer[i]->missnum >= missnum) { needpost = true; }
      if (std::chrono::duration<double>(std::chrono::high_resolution_clock::now()-g_idlestart).count() > vacuump) {
        needpost = true;
      }

      auto start = std::chrono::high_resolution_clock::now();

      boost::property_tree::ptree json, rejson;
      std::string host = "http://192.168.1.158:10087/post";

      if (needpost) {
        std::string portrait = Mat2Base64(postbuffer[i]->portrait, "jpg");
        json.put<int>("camid", postbuffer[i]->camid);
        json.put<std::string>("portrait", portrait);

        //auto now = std::chrono::system_clock::now();
        //std::string timestamp = chrono_to_string(now);
        //json.put<std::string>("timestamp", timestamp);

        if (postbuffer[i]->score > cmpthre) {
          json.put<int>("picId", postbuffer[i]->picid);
          json.put<int>("prisonerId",postbuffer[i]->personid);
          json.put<float>("score", postbuffer[i]->score);
          //host = knownhost;
        }

        rejson.add_child("data", json);
        std::string post = json2string(rejson);

        std::string resp;
        int code = client->Post(host, post, resp);

        std::cout << "[INFO]: post. @@@@@@@@ [round time]: " << std::chrono::duration<double, std::milli>(std::chrono::high_resolution_clock::now()-start).count() << "ms" << std::endl;
        //erase
        postbuffer.erase(postbuffer.begin()+i);
        i--;
      }
    }

  }
}


int main(int argc, char** argv)
{
  g_iniparser = new IniParser();
  int port = g_iniparser->GetInt("port", "setting");

  //model
  g_fdetector = new DarknetYolo("models/mbv2_yolov3_face_feats.cfg", "models/mbv2_yolov3_face_final.weights");
  //g_fdetector = new DarknetYolo("models/mbv2_yolov3_face.cfg", "models/mbv2_yolov3_face_final.weights");
  g_finsight = new CaffeInsight("models/insightface/deploy.prototxt", "models/insightface/inference.caffemodel", "", "0,0,0");


  //offline database
  int useofflineft = g_iniparser->GetInt("useofflineft", "setting");

  if (useofflineft > 0) {
    std::cout << "[INFO]: load offline features." << std::endl;
    std::ifstream readFile("models/insightface/feats.txt");

    std::string line;
    if (readFile) {
      while (getline(readFile,line)) {
        std::cout << line << "\n" << std::endl;

        float feature[1024];
        StringToFloat1024v1(line, feature);
        std::cout << feature[0] << " " << feature[1023] << std::endl;

        DBFace face;
        memcpy(face.feature, feature, 1024*sizeof(float));
        face.personid = g_DBFaceVec.size()+1;
        face.picid = g_DBFaceVec.size()+1;
        g_DBFaceVec.push_back(face);
      }
    }

    std::cout << "count: " << g_DBFaceVec.size() << " " << g_DBFaceVec[g_DBFaceVec.size()-1].feature[0] << " " << g_DBFaceVec[g_DBFaceVec.size()-1].feature[1023] << std::endl;
  }

  //align points
  g_stdAlignPoints.push_back(cv::Point2f(30.2946, 51.6963));
  g_stdAlignPoints.push_back(cv::Point2f(65.5318, 51.6963));
  g_stdAlignPoints.push_back(cv::Point2f(48.0252, 71.7366));
  g_stdAlignPoints.push_back(cv::Point2f(33.5493, 92.3655));
  g_stdAlignPoints.push_back(cv::Point2f(62.7299, 92.3655));

  g_stdPostPoints.push_back(cv::Point2f(48 + 30.2946, 56 + 51.6963));
  g_stdPostPoints.push_back(cv::Point2f(48 + 65.5318, 56 + 51.6963));
  g_stdPostPoints.push_back(cv::Point2f(48 + 48.0252, 56 + 71.7366));
  g_stdPostPoints.push_back(cv::Point2f(48 + 33.5493, 56 + 92.3655));
  g_stdPostPoints.push_back(cv::Point2f(48 + 62.7299, 56 + 92.3655));

  //mean value for 1024 dims feature comparison
  std::string filename = "models/insightface/FeatsMean.yml";
  cv::FileStorage fs(filename, cv::FileStorage::READ);
  fs["Mean"] >> FEATURE_MEANVAL;
  fs.release();


  //mutex
  pthread_mutex_init(&culock,NULL);
  pthread_mutex_init(&dblock,NULL);
  pthread_mutex_init(&qelock,NULL);
  pthread_mutex_init(&stlock,NULL);

  //holding threads
  pthread_t thd1,thd2,thd3,thd4;
  pthread_t thd5,thd6,thd7,thd8;

  pthread_create(&thd1, NULL, thread_detection, NULL);
  pthread_create(&thd2, NULL, thread_insightface, NULL);
  pthread_create(&thd3, NULL, thread_compare, NULL);
  //pthread_create(&thd4, NULL, thread_post, NULL);

  pthread_create(&thd5, NULL, thread_tcp_listen, NULL);
  pthread_create(&thd6, NULL, thread_tcp_image, NULL);
  pthread_create(&thd7, NULL, thread_tcp_post_listen, NULL);
  pthread_create(&thd8, NULL, thread_tcp_post, NULL);

  pthread_detach(thd1);
  pthread_detach(thd2);
  pthread_detach(thd3);
  //pthread_detach(thd4);

  pthread_detach(thd5);
  pthread_detach(thd6);
  pthread_detach(thd7);
  pthread_detach(thd8);


  //httpserver
  webserver ws = create_webserver(port);
  std::cout << "Http Server is Running on: " << port << std::endl;

  feature_calculation fc;
  ws.register_resource("/face/featurecal", &fc);
  face_recognition fr;
  ws.register_resource("/face/recognition", &fr);
  face_analysis fa;
  ws.register_resource("/face/analysis", &fa);

  ws.start(true);

  pthread_mutex_destroy(&culock);
  pthread_mutex_destroy(&dblock);
  pthread_mutex_destroy(&qelock);
  pthread_mutex_destroy(&stlock);
  return 0;
}
